/**
 * @file     AnalyzeStructures.cpp
 * @brief    AnalyzeStructures implementation
 *
 * @author   Franco Riberi
 * @email    fgriberi AT gmail.com
 *
 * Contents: Header file for titere providing AnalyzeStructure implementation.
 *
 * System:   titere: Target Ideal TEmperature Rna Expression
 * Language: C++

 * @date January 8, 2014
 *
 * Copyright (C) 2014 Franco Riberi, FuDePAN
 *
 * This file is part of the titere.
 *
 * Titere is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * Titere is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with titere. If not, see <http://www.gnu.org/licenses/>.
 *
 */

#include <mili/mili.h>
#include <biopp-filer/bioppFiler.h>
#include "titere/Types.h"
#include "titere/Exceptions.h"
#include "titere/PredicateRule.h"
#include "titere/TitereHelper.h"
#include "titere/WindowsRules.h"
#include "titere/AnalyzeStructures.h"

using mili::operator>>;

/**
 * @brief Temporal methods to execute titere
 */
fideo::IFold* getDerivedFold(const std::string& derivedKey);

namespace NSTitere
{

const char AnalyzeStructure::OUTPUT_FILE_NAME[]                   = "outputTitere.csv";
const char AnalyzeStructure::START_WITH_ADENINE_OR_URACIL_RULE[]  = "StartWithAdenineOrUracil";
const char AnalyzeStructure::FIRST_EIGHT_NUC_RULE[]               = "FirstEigthNucleotide";
const char AnalyzeStructure::NUC_NINETEEN_RULE[]                  = "NucleotidNineteen";

AnalyzeStructure::AnalyzeStructure(const TitereInputArgs& args)
    : _args(args), _outputFile(OUTPUT_FILE_NAME)
{
    _folder = getDerivedFold(args.folder);
    setRules();
}

AnalyzeStructure::~AnalyzeStructure()
{
    delete _folder;
    _folder = NULL;

    mili::delete_container(_rules);
}

/** @brief Register the rules */
REGISTER_FACTORIZABLE_CLASS(IPredicateRule, StartWithAdenineOrUracil, std::string, AnalyzeStructure::START_WITH_ADENINE_OR_URACIL_RULE);
REGISTER_FACTORIZABLE_CLASS(IPredicateRule, FirstEigthNucleotide, std::string, AnalyzeStructure::FIRST_EIGHT_NUC_RULE);
REGISTER_FACTORIZABLE_CLASS(IPredicateRule, NucleotidNineteen, std::string, AnalyzeStructure::NUC_NINETEEN_RULE);

void AnalyzeStructure::setRules()
{
    mili::insert_into(_rules, Rule::new_class(START_WITH_ADENINE_OR_URACIL_RULE));
    mili::insert_into(_rules, Rule::new_class(FIRST_EIGHT_NUC_RULE));
    mili::insert_into(_rules, Rule::new_class(NUC_NINETEEN_RULE));
}

void AnalyzeStructure::foldSequences()
{
    bioppFiler::FastaParser<biopp::NucSequence> rnaMsgFile(_args.fileNameRNAm);
    biopp::NucSequence currentSequence;
    std::string currentDescription;
    SequencesFoldedContainer containerSequencesFolded;
    bool existAnotherSequence = rnaMsgFile.getNextSequence(currentDescription, currentSequence);
    while (existAnotherSequence)
    {
        containerSequencesFolded._description = currentDescription;
        containerSequencesFolded._sequence = currentSequence;
        foldInAllTemps(currentSequence, containerSequencesFolded);
        mili::insert_into(_sequencesFolded, containerSequencesFolded);
        containerSequencesFolded._structures.clear();
        existAnotherSequence = rnaMsgFile.getNextSequence(currentDescription, currentSequence);
    }
}

void AnalyzeStructure::foldInAllTemps(const biopp::NucSequence& sequence, SequencesFoldedContainer& currentFolded)
{
    biopp::SecStructure secondaryStructure;
    for (Temperature temp(_args.initialRangeTemp); temp <= _args.finalRangeTemp; ++temp)
    {
        _folder->fold(sequence, _args.circular, secondaryStructure, temp);
        mili::insert_into(currentFolded._structures, secondaryStructure);
        secondaryStructure.clear();
    }
}

bool AnalyzeStructure::checkRules(const biopp::NucSequence& sequence)
{
    Rules::const_iterator rule = _rules.begin();
    bool ruleIsMet;
    do
    {
        ruleIsMet = (*rule++)->checkRule(sequence);
    }
    while (ruleIsMet && rule != _rules.end());
    return ruleIsMet;
}

void AnalyzeStructure::getSubSequence(const Position from, const Position to, const biopp::NucSequence& originalSeq, biopp::NucSequence& subSequence) const
{
    mili::assert_throw<InvalidPosition>(to < originalSeq.length());

    const std::string sequenceToString(originalSeq.getString());
    const std::string subSeq(mili::substr(sequenceToString, mili::Pos_(from), mili::Pos_(to)));
    const biopp::NucSequence tempSeq(subSeq);
    subSequence = tempSeq;
}

void AnalyzeStructure::processWindow(const Position pos0, const SequencesFoldedContainer& seqFoldContainer)
{
    mili::assert_throw<InvalidSize>(MINIMAL_AMOUNT_OF_STRUCTURES <= seqFoldContainer._structures.size());

    biopp::SecStructure referenceStructure = seqFoldContainer._structures.front();
    biopp::NucSequence currentWindow;

    const Position pos1 = pos0 + _args.lengthTolerance;
    getSubSequence(pos0, pos1, seqFoldContainer._sequence, currentWindow);
    if (checkRules(currentWindow))
    {
        //NOTE: for all the temperatures in the range except the reference temperature
        size_t structIndex(1u);
        std::string diffStruct;
        NucleotidsAmount amoutOfUnpaired;
        NucleotidsAmount amountHeadUnpaired;
        std::string seqName;
        for (Temperature temp(_args.initialRangeTemp + 1); temp <= _args.finalRangeTemp; ++temp)
        {
            calculateDerivedStructureData(pos0, pos1, referenceStructure, seqFoldContainer._structures[structIndex++], diffStruct);
            calculateUnpaired(referenceStructure, amoutOfUnpaired, amountHeadUnpaired);
            getDescriptionName(seqFoldContainer._description, seqName);
            _outputFile.save(seqName, pos0, temp, amoutOfUnpaired, amountHeadUnpaired, diffStruct);
            diffStruct.clear();
        }
    }
}

void AnalyzeStructure::processSequence(const SequencesFoldedContainer& seqFoldContainer)
{
    const Position lastPos = seqFoldContainer._sequence.length() - _args.lengthTolerance;
    for (Position pos0(0u); pos0 < lastPos; ++pos0)
    {
        processWindow(pos0, seqFoldContainer);
    }
}

void AnalyzeStructure::processPairedOnReferenceStruct(const Position index, const biopp::SecStructure& currentStructure,
        const biopp::SecStructure& referenceStruct, std::string& diffStruct)
{
    if (currentStructure.is_paired(index))
    {
        const biopp::SeqIndex nucPairedStrucReference = referenceStruct.paired_with(index);
        const biopp::SeqIndex nucPairedCurrentStruct = currentStructure.paired_with(index);
        if (nucPairedStrucReference == nucPairedCurrentStruct)
        {
            diffStruct += SamePairing;
        }
        else
        {
            diffStruct += PairedCrossed;
        }
    }
    else
    {
        diffStruct += UnpairedToPaired;
    }
}

void AnalyzeStructure::processUnpairedOnReferenceStruct(const Position index, const biopp::SecStructure& structure, std::string& diffStruct)
{
    if (structure.is_paired(index))
    {
        diffStruct += PairedToUnpaired;
    }
    else
    {
        diffStruct += SamePairing;
    }
}

void AnalyzeStructure::calculateDerivedStructureData(const Position from, const Position to, const biopp::SecStructure& referenceStruct,
        const biopp::SecStructure& currentStructure, std::string& diffStruct)
{
    diffStruct.clear();
    for (Position i(from); i <= to; ++i)
    {
        if (referenceStruct.is_paired(i))
        {
            processPairedOnReferenceStruct(i, currentStructure, referenceStruct, diffStruct);
        }
        else
        {
            processUnpairedOnReferenceStruct(i, currentStructure, diffStruct);
        }
    }
}

void AnalyzeStructure::calculateUnpaired(const biopp::SecStructure structure, NucleotidsAmount& unpairedCounter, NucleotidsAmount& headUnpairedCounter)
{
    const Position lastPos = structure.size() - 1u;
    unpairedCounter = countNucleotidesUnpaired(INITIAL_POSITION, lastPos, structure);
    headUnpairedCounter = countNucleotidesUnpaired(SECOND_POSITION, EIGHT_POSITION, structure);
}

NucleotidsAmount AnalyzeStructure::countNucleotidesUnpaired(const Position from, const Position to, const biopp::SecStructure& structure)
{
    mili::assert_throw<InvalidPosition>(to < structure.size());

    NucleotidsAmount amountUnpaired(0u);
    for (Position i(from); i <= to; ++i)
    {
        if (!structure.is_paired(i))
        {
            ++amountUnpaired;
        }
    }
    return amountUnpaired / 2u;
}

void AnalyzeStructure::analyzeWindows()
{
    for (auto& seq : _sequencesFolded)
    {
        processSequence(seq);
    }
}

} // namespace NSTitere
